package repository

import (
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	bkeinit "gopkg.openfuyao.cn/bkecommon/cluster/initialize"
	"gopkg.openfuyao.cn/bkecommon/cluster/validation"
	"gopkg.openfuyao.cn/bkecommon/warehouse"

	"gopkg.openfuyao.cn/bkeadm/pkg/global"
	"gopkg.openfuyao.cn/bkeadm/pkg/registry"
	"gopkg.openfuyao.cn/bkeadm/pkg/root"
	"gopkg.openfuyao.cn/bkeadm/utils"
	"gopkg.openfuyao.cn/bkeadm/utils/log"
)

var (
	sourceRegistry = fmt.Sprintf("%s/mount/source_registry/files", global.Workspace)
)

type OtherRepo struct {
	Repo   string `json:"repo"`
	RepoIP string `json:"repoIP"`
	Image  string `json:"image"`
	Source string `json:"source"`
}

func ParseOnlineConfig(domain, repo, source string) (OtherRepo, error) {
	o := OtherRepo{}
	if len(source) > 0 {
		o.Source = source
	}
	if len(repo) > 0 {
		o.Repo = repo
		// 判断是否为镜像
		repos := strings.Split(repo, "/")
		if strings.Contains(repos[len(repos)-1], ":") {
			o.Image = repo
		}
		if len(o.Image) > 0 {
			index := strings.LastIndex(o.Repo, "/")
			if index != -1 {
				o.Repo = repo[:index]
			}
		}

		repos2 := strings.Split(repos[0], ":")
		if net.ParseIP(repos2[0]) != nil {
			o.RepoIP = repos2[0]
			o.Repo = strings.Replace(o.Repo, o.RepoIP, domain, -1)
			o.Image = strings.Replace(o.Image, o.RepoIP, domain, -1)
		} else {
			ip4, err := utils.LoopIP(repos2[0])
			if err != nil {
				return o, err
			}
			if len(ip4) == 0 {
				return o, errors.New(fmt.Sprintf("Domain name resolution failure, %s", repos2[0]))
			}
			o.RepoIP = ip4[0]
		}
		if !strings.HasSuffix(o.Repo, "/") {
			o.Repo = o.Repo + "/"
		}
	}
	return o, nil
}

func SourceInit(oc OtherRepo) error {
	if len(oc.Source) == 0 {
		return nil
	}
	if !utils.Exists(sourceRegistry) {
		err := os.MkdirAll(sourceRegistry, 0644)
		if err != nil {
			return err
		}
	}
	err := sourceBaseFile(oc.Source)
	if err != nil {
		return err
	}
	err = sourceRuntime(oc.Source)
	if err != nil {
		return err
	}
	_ = sourceRuntimeKylin(oc.Source)
	return nil
}

type CertificateConfig struct {
	TLSVerify    bool
	CAFile       string
	Username     string
	Password     string
	RegistryHost string // 新增：镜像仓库主机名
	RegistryPort string // 新增：镜像仓库端口
}

// 修改：根据实际域名设置CA证书（单向认证）
func SetupCACertificate(config *CertificateConfig) error {
	if config.CAFile == "" {
		return nil
	}

	// 定义需要处理的证书目录列表
	certDirs := []string{
		fmt.Sprintf("/etc/containerd/certs.d/%s:%s", config.RegistryHost, config.RegistryPort),
		fmt.Sprintf("/etc/containerd/certs.d/%s", config.RegistryHost),
	}

	// 循环处理所有目录：创建目录并复制CA证书
	for _, dir := range certDirs {
		// 创建目录（含父目录）
		if err := os.MkdirAll(dir, utils.DefaultDirPermission); err != nil {
			return err
		}
		// 复制CA证书到目标目录
		destPath := filepath.Join(dir, "ca.crt")
		if err := copyFile(config.CAFile, destPath); err != nil {
			return err
		}
	}

	return nil
}

func copyFile(src, dst string) error {
	srcFile, err := os.Open(src)
	if err != nil {
		return err
	}
	defer srcFile.Close()

	dstFile, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer dstFile.Close()

	_, err = io.Copy(dstFile, srcFile)
	return err
}

// 新增：从镜像仓库URL解析主机名和端口
func ParseRegistryHostPort(imageRepo string) (host, port string) {
	if imageRepo == "" {
		return "", ""
	}

	// 移除协议前缀
	repo := strings.TrimPrefix(imageRepo, "http://")
	repo = strings.TrimPrefix(repo, "https://")

	// 分割主机和路径
	parts := strings.Split(repo, "/")
	if len(parts) == 0 {
		return "", ""
	}

	// 分割主机和端口
	hostPort := strings.Split(parts[0], ":")
	if len(hostPort) == 1 {
		// 没有端口，使用默认端口
		return hostPort[0], "443"
	} else if len(hostPort) == 2 {
		return hostPort[0], hostPort[1]
	}

	return "", ""
}

func RepoInit(oc OtherRepo, certConfig *CertificateConfig) error {
	if len(oc.Image) == 0 {
		return nil
	}
	if utils.Exists(yumDataFile) {
		return nil
	}
	if utils.Exists(yumDataFile + ".temp") {
		err := os.RemoveAll(yumDataFile + ".temp")
		if err != nil {
			return err
		}
	}

	// 解析镜像仓库的主机名和端口
	registryHost, registryPort := ParseRegistryHostPort(oc.Image)
	certConfig.RegistryHost = registryHost
	certConfig.RegistryPort = registryPort

	od := registry.OptionsDownload{
		Options:             root.Options{},
		SrcTLSVerify:        certConfig.TLSVerify,
		Image:               oc.Image,
		Username:            certConfig.Username,
		Password:            certConfig.Password,
		CertDir:             certConfig.CAFile,
		DownloadToDir:       yumDataFile + ".temp",
		DownloadInImageFile: "source.tar.gz",
	}
	// 需要单向校验
	if certConfig.TLSVerify {
		err := SetupCACertificate(certConfig)
		if err != nil {
			return err
		}
		log.BKEFormat(log.INFO, "Using client certificate authentication(CA only)")
	}

	if strings.Contains(oc.Image, bkeinit.DefaultImageRepo) {
		od.SrcTLSVerify = true
		img1 := strings.Split(oc.Image, "/")
		img2 := strings.Split(img1[0], ":")
		if len(img2) != 2 {
			return errors.New(fmt.Sprintf("The domain name and port must be included, %s", oc.Image))
		}
		err := warehouse.SetClientCertificate(img2[1])
		if err != nil {
			return err
		}
		od.CertDir = fmt.Sprintf("/etc/docker/certs.d/deploy.bocloud.k8s:%s", img2[1])
	}
	log.BKEFormat(log.INFO, "Download source file...")
	err := od.Download()
	if err != nil {
		return err
	}
	err = os.Rename(yumDataFile+".temp/source.tar.gz", yumDataFile)
	if err != nil {
		return err
	}
	_ = os.RemoveAll(yumDataFile + ".temp")
	return nil
}

func sourceBaseFile(httpRepo string) error {
	if !utils.Exists(chartDataFile) {
		log.BKEFormat(log.INFO, fmt.Sprintf("download %s/files/chart.tar.gz", httpRepo))
		err := utils.DownloadFile(httpRepo+"/files/charts.tar.gz", chartDataFile)
		if err != nil {
			log.BKEFormat(log.WARN, fmt.Sprintf("Failed to download chart.tar.gz package: %s", err))
		}
	}
	if !utils.Exists(nfsDataFile) {
		log.BKEFormat(log.INFO, fmt.Sprintf("download %s/files/nfsshare.tar.gz", httpRepo))
		if utils.Exists(nfsDataFile + ".temp") {
			err := os.RemoveAll(nfsDataFile + ".temp")
			if err != nil {
				return err
			}
		}
		err := utils.DownloadFile(httpRepo+"/files/nfsshare.tar.gz", nfsDataFile+".temp")
		if err != nil {
			log.BKEFormat(log.WARN, fmt.Sprintf("Failed to download nfsshare.tar.gz package: %s", err))
		}
		err = os.Rename(nfsDataFile+".temp", nfsDataFile)
		if err != nil {
			return err
		}
	}
	return nil
}

// downloadRuntime download containerd cni
func sourceRuntime(httpRepo string) error {
	containerdList := []string{}
	cniPluginList := []string{}
	kubectlList := []string{}
	entries, err := os.ReadDir(sourceRegistry)
	if err != nil {
		return err
	}
	for _, entry := range entries {
		if strings.HasPrefix(entry.Name(), "kubectl") {
			kubectlList = append(kubectlList, entry.Name())
			continue
		}

		if strings.HasPrefix(entry.Name(), utils.CniPluginPrefix) {
			cniPluginList = append(cniPluginList, entry.Name())
			continue
		}
		if err = validation.ValidateCustomExtra(map[string]string{"containerd": entry.Name()}); err != nil {
			continue
		}
		containerdList = append(containerdList, entry.Name())
	}

	if len(containerdList) > 0 && len(cniPluginList) > 0 && len(kubectlList) > 0 {
		return nil
	}

	httpRepo = httpRepo + "/files/"
	containerd := []string{}
	cni := []string{}
	kubectl := []string{}

	resp, err := http.Get(httpRepo)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return errors.New(fmt.Sprintf(" get url %s, status code %d", httpRepo, resp.StatusCode))
	}
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	htmlData := string(body)
	if len(htmlData) == 0 {
		return errors.New(fmt.Sprintf("url: %s, Failed to get download list", httpRepo))
	}
	re := regexp.MustCompile("<a href=\"(.*?)\">(.*?)</a>")
	result := re.FindAllStringSubmatch(htmlData, -1)

	for _, res := range result {
		if len(res) < 2 {
			continue
		}
		if strings.HasPrefix(res[1], "containerd") {
			containerd = append(containerd, res[1])
		}
		if strings.HasPrefix(res[1], utils.CniPluginPrefix) {
			cni = append(cni, res[1])
		}

		// 新增：筛选以 kubectl 开头的文件（如 kubectl-v1.33.1-amd64）
		if strings.HasPrefix(res[1], "kubectl-") {
			kubectl = append(kubectl, res[1]) // 新增 kubectlFiles 列表
		}
	}
	for _, con := range containerd {
		log.BKEFormat(log.INFO, fmt.Sprintf("download %s", httpRepo+con))
		err = utils.DownloadFile(httpRepo+con, sourceRegistry+"/"+con)
		if err != nil {
			return err
		}
	}
	for _, cn := range cni {
		log.BKEFormat(log.INFO, fmt.Sprintf("download %s", httpRepo+cn))
		err = utils.DownloadFile(httpRepo+cn, sourceRegistry+"/"+cn)
		if err != nil {
			return err
		}
	}
	// 新增：下载 kubectl（若有符合条件的文件）
	if len(kubectl) > 0 {
		for _, kubectlFile := range kubectl {
			log.BKEFormat(log.INFO, fmt.Sprintf("download %s", httpRepo+kubectlFile))
			err = utils.DownloadFile(httpRepo+kubectlFile, sourceRegistry+"/"+kubectlFile)
			if err != nil {
				return fmt.Errorf("failed to download kubectl file %s: %w", kubectlFile, err)
			}
		}
	} else {
		// 若内部源无 kubectl 文件，返回明确错误（便于排查）
		return errors.New("no kubectl files found in http repo: " + httpRepo)
	}
	return nil
}

func sourceRuntimeKylin(httpRepo string) error {
	httpRepo = httpRepo + "/files/"
	result := []string{}
	result = append(result, strings.Replace(utils.KylinDocker, "{.arch}", "arm64", -1))
	result = append(result, strings.Replace(utils.KylinDocker, "{.arch}", "amd64", -1))
	for _, res := range result {
		if !utils.Exists(res) {
			log.BKEFormat(log.INFO, fmt.Sprintf("download %s", httpRepo+res))
			err := utils.DownloadFile(httpRepo+res, sourceRegistry+"/"+res)
			if err != nil {
				log.Errorf("Failed to download %s: %s", res, err)
				return err
			}
		}
	}
	return nil
}
